package com.hmdp.utils;

import cn.hutool.core.util.HashUtil;
import org.springframework.data.redis.core.StringRedisTemplate;

/**
 * 布隆过滤器
 * 因为java的bitmap和redis的无法直接对接,所以这里只提供hash结果的返回
 * @author wanghaidong
 * @date 2023/08/10
 */
public class BloomFilter {
    private String cachePrefix;
    private StringRedisTemplate stringRedisTemplate;
    private int size;

    public BloomFilter(StringRedisTemplate stringRedisTemplate, String cachePrefix, int size) {
        this.stringRedisTemplate = stringRedisTemplate;
        this.cachePrefix = cachePrefix;
        this.size = size;
    }

    public void add(String value) {
        long offset1 = hash1(value) % size;
        long offset2 = hash2(value) % size;
        long offset3 = hash3(value) % size;
        long offset4 = hash4(value) % size;
        long offset5 = hash5(value) % size;
        stringRedisTemplate.opsForValue().setBit(cachePrefix + ":hash1", offset1, true);
        stringRedisTemplate.opsForValue().setBit(cachePrefix + ":hash2", offset2, true);
        stringRedisTemplate.opsForValue().setBit(cachePrefix + ":hash3", offset3, true);
        stringRedisTemplate.opsForValue().setBit(cachePrefix + ":hash4", offset4, true);
        stringRedisTemplate.opsForValue().setBit(cachePrefix + ":hash5", offset5, true);
    }

    public boolean contains(String value) {
        long offset = hash1(value) % size;
        Boolean result = stringRedisTemplate.opsForValue().getBit(cachePrefix + ":hash1", offset);
        if (result == null || !result) {
            return false;
        }
        offset = hash2(value) % size;
        result = stringRedisTemplate.opsForValue().getBit(cachePrefix + ":hash2", offset);
        if (result == null || !result) {
            return false;
        }
        offset = hash3(value) % size;
        result = stringRedisTemplate.opsForValue().getBit(cachePrefix + ":hash3", offset);
        if (result == null || !result) {
            return false;
        }
        offset = hash4(value) % size;
        result = stringRedisTemplate.opsForValue().getBit(cachePrefix + ":hash4", offset);
        if (result == null || !result) {
            return false;
        }
        offset = hash5(value) % size;
        result = stringRedisTemplate.opsForValue().getBit(cachePrefix + ":hash5", offset);
        if (result == null || !result) {
            return false;
        }

        return true;
    }

    private long hash1(String value) {
        return (long) HashUtil.javaDefaultHash(value) % this.size;
    }

    private long hash2(String value) {
        return (long)HashUtil.elfHash(value) % this.size;
    }

    private long hash3(String value) {
        int hash = 1315423911;

        for(int i = 0; i < value.length(); ++i) {
            hash ^= (hash << 5) + value.charAt(i) + (hash >> 2);
        }

        if (hash < 0) {
            hash *= -1;
        }

        return (long)hash % this.size;
    }

    private long hash4(String value) {
        return (long) HashUtil.pjwHash(value) % this.size;
    }

    private long hash5(String value) {
        return (long) HashUtil.sdbmHash(value) % this.size;
    }
}
